package affd.logic;

import java.util.ArrayList;

import net.htmlparser.jericho.CharacterReference;

/**
 * A Token is a string of characters which is categorized by type. Possible token types
 * are WORD (contains only letters), WHITE_SPACE (whitespace characters), 
 * PUNCTUATION (Punctuation marks) and UNKNOWN (The token type is undefined).
 * 
 * @author Joteitti joteitti@cc.hut.fi
 *
 */
public class Token {
	
	public static final String HTML_TAG_ADDRESS = "a";
	public static final String HTML_TAG_SPAN = "span";
	public static final String HTML_ATTRIBUTE_HREF = "href";
	public static final String HTML_ATTRIBUTE_HREF_PREFIX = "#";
	public static final String HTML_ATTRIBUTE_HREF_POSTFIX = "cell";
	public static final String HTML_ATTRIBUTE_CLASS = "class";
	public static final String HTML_ATTRIBUTE_ORIGINAL = "original";
	public static final String HTML_ATTRIBUTE_GROUP_ID = "group";
	public static final String HTML_ATTRIBUTE_GROUP_ID_PREFIX = "group";
	public static final String HTML_ATTRIBUTE_ID = "id";
	public static final String HTML_ATTRIBUTE_ID_PREFIX = "token";
	public static final String HTML_ATTRIBUTE_UNCERTAIN = "uncertain";
	public static final String HTML_ATTRIBUTE_UNCERTAIN_PREFIX = "yes";
	
	private static int idCount = 0;
	private static int groupIdCount = 0;
	
	public enum Type {
		WORD, WHITE_SPACE, PUNCTUATION, UNKNOWN
	}

	private String original;
	private String content;
	private boolean uncertain;
	private Sentence parent;
	private Type type;
	private HtmlTag tag;
	private HtmlTag reference;
	
	/**
	 * Constructs a token.
	 * 
	 * @param content Content to be added.
	 * @param type Type of the token.
	 */
	public Token(String content, Type type) {
		this.content = content;
		this.type = type;
		this.uncertain = false;
		
		// Store the original content
		this.original = content;
	}

	/**
	 * Gets the next sibling Token of this Token
	 * 
	 * @return Token which is the next in the sentence from this token. If this is
	 * the last token in the parent sentence, the method returns null.
	 */
	public Token getNextToken() {
		ArrayList<Token> sentenceTokens = getParent().getTokens();
		int index = sentenceTokens.indexOf(this);
		if (index + 1 == sentenceTokens.size())
			return null;
		return sentenceTokens.get(index + 1);	
	}
	
	/**
	 * Tests if the token's content is modified.
	 * 
	 * @return True if the token is modified otherwise false.
	 */
	public boolean isContentModified() {
		return !original.equals(content);
	}
	
	/**
	 * Tests if the token's content is modified.
	 * 
	 * @return True if the token is modified otherwise false.
	 */
	public boolean isContentUncertain() {
		return uncertain;
	}
	
	/**
	 * Merges the specified token to this.
	 * 
	 * @param token Token to be merged.
	 */
	public void merge(Token token) {
		// TODO Merge other content ?
		original += token.original;
	}
	
	/**
	 * Indicates that the content of the token is modified. This method generates automatically
	 * the proper Html tag for the token. If content group is set to null no group will be assigned.
	 * 
	 * @param content Content to be added.
	 * @param modificationClass Class to be added.
	 * @param contentGroup Group to be added
	 */
	public void modifyContent(String content, String modificationClass, String contentGroup ) {
		this.content = content;
		this.uncertain = false;
		
		// Generate the Html tag
		tag = new HtmlTag(HTML_TAG_SPAN);
		tag.addAttribute(HTML_ATTRIBUTE_ID, generateId());
		tag.addAttribute(HTML_ATTRIBUTE_CLASS, modificationClass);
		tag.addAttribute(HTML_ATTRIBUTE_ORIGINAL, original);
		
		if(contentGroup != null) {
			tag.addAttribute(HTML_ATTRIBUTE_GROUP_ID, contentGroup);
			
			// Create group reference
			reference = new HtmlTag(HTML_TAG_ADDRESS);
			
			// Create href value
			StringBuffer hrefValue = new StringBuffer();
			hrefValue.append(HTML_ATTRIBUTE_HREF_PREFIX);
			hrefValue.append(contentGroup);
			hrefValue.append(HTML_ATTRIBUTE_HREF_POSTFIX);
			
			reference.addAttribute(HTML_ATTRIBUTE_HREF, hrefValue.toString());
		}
		else {
			reference = null;
		}
	}
	
	/**
	 * Indicates that the content of the token is uncertain. This method generates automatically
	 * the proper Html tag for the token. If content group is set to null no group will be assigned.
	 * 
	 * @param modificationClass Class to be added.
	 * @param contentGroup Group to be added
	 */
	public void uncertainContent(String modificationClass, String contentGroup ) {
		this.uncertain = true;
		
		// Generate the Html tag
		tag = new HtmlTag(HTML_TAG_SPAN);
		tag.addAttribute(HTML_ATTRIBUTE_ID, generateId());
		tag.addAttribute(HTML_ATTRIBUTE_CLASS, modificationClass);
		tag.addAttribute(HTML_ATTRIBUTE_UNCERTAIN, HTML_ATTRIBUTE_UNCERTAIN_PREFIX);

		if(contentGroup != null) {
			tag.addAttribute(HTML_ATTRIBUTE_GROUP_ID, contentGroup);
			
			// Create group reference
			reference = new HtmlTag(HTML_TAG_ADDRESS);
			
			// Create href value
			StringBuffer hrefValue = new StringBuffer();
			hrefValue.append(HTML_ATTRIBUTE_HREF_PREFIX);
			hrefValue.append(contentGroup);
			hrefValue.append(HTML_ATTRIBUTE_HREF_POSTFIX);
			
			reference.addAttribute(HTML_ATTRIBUTE_HREF, hrefValue.toString());
		}
		else {
			reference = null;
		}
	}
	
	/**
	 * Returns the content of the token.
	 * 
	 * @return Content of token.
	 */
	public String getContent() {
		return content;
	}
	
	/**
	 * Returns the type of the token.
	 * 
	 * @return Type of the token.
	 */
	public Type getType() {
		return type;
	}
	
	/**
	 * Returns the Html tag of the token.
	 * 
	 * @return Html tag of the token if specified otherwise null.
	 */
	public HtmlTag getTag() {
		return tag;
	}
	
	public String getHtml(boolean ignoreTags)
	{
		String encoded = CharacterReference.encode(content);
		
		// Add tags if modified or uncertain
		if(!ignoreTags) {
			if(isContentModified() || isContentUncertain()) {
				encoded = tag.assign(encoded);
				
				// Add reference
				if(reference != null) {
					encoded = reference.assign(encoded);
				}
			}
		}
		
		return encoded;
	}
	
	/**
	 * Returns the original content of the token.
	 * 
	 * @return The original content of the token.
	 */
	public String getOriginal() {
		return original;
	}
	
	/**
	 * Sets the parent sentence of the token.
	 * 
	 * @param sentence Sentence to be set.
	 */
	public void setParent(Sentence sentence) {
		parent = sentence;
	}
	
	/**
	 * Returns the parent sentence of the token.
	 * 
	 * @return Parent sentence of the token if specified otherwise null.
	 */
	public Sentence getParent() {
		return parent;
	}
	
	/**
	 * Returns a string representation of this token.
	 */
	public String toString() {
		return content;
	}
	
	/**
	 * Generates unique token id.
	 * 
	 * @return Token id.
	 */
	public static String generateId() {
		String id = HTML_ATTRIBUTE_ID_PREFIX + Integer.toString(idCount);
		idCount = idCount + 1;
		
		return id;
	}
	
	/**
	 * Generates unique token group id.
	 * 
	 * @return Token id.
	 */
	public static String generateGroupId() {
		String id = HTML_ATTRIBUTE_GROUP_ID_PREFIX + Integer.toString(idCount);
		groupIdCount = groupIdCount + 1;
		
		return id;
	}
	
	/**
	 * Represents a Html span tag. Tag is used to store token information.
	 * 
	 * @author Joteitti joteitti@cc.hut.fi
	 *
	 */
	public static class HtmlTag {

		protected String name;
		protected ArrayList<HtmlAttribute> attributes;
		
		/**
		 * Constructs a Html tag. Each tag has an unique identifier.
		 * 
		 * @param className Name of the class to be added.
		 */
		public HtmlTag(String name) {
			this.name = name;
			
			// Create attribute list
			attributes = new ArrayList<HtmlAttribute>();
		}
		
		/**
		 * Adds a specified attribute.
		 * 
		 * @param name Name to be added.
		 * @param value Value to be added.
		 */
		public void addAttribute(String name, String value) {
			attributes.add(new HtmlAttribute(name, value));
		}
		
		/**
		 * Returns the value of the attribute.
		 * 
		 * @param name Attribute value to find.
		 * @return Attribute value if found otherwise null.
		 */
		public String getAttribute(String name) {
			for(HtmlAttribute attribute : attributes) {
				if(attribute.name.equalsIgnoreCase(name)) {
					return attribute.value;
				}
			}
			
			return null;
		}
		
		/**
		 * Assigns the Html tag with attributes to the specified text.
		 * 
		 * @param text Text to be processed.
		 * @return Text with Html tag assigned.
		 */
		public String assign(String text) {
			// Create begin tag
			StringBuffer tagBegin = new StringBuffer();
			tagBegin.append("<" + name);
			for(HtmlAttribute attribute : attributes) {
				tagBegin.append(" " + attribute.toString());
			}
			tagBegin.append(">");
			
			// Create end tag
			StringBuffer tagEnd = new StringBuffer();
			tagEnd.append("</" + name + ">");
			
			return tagBegin.toString() + text + tagEnd.toString();
		}
		
		/**
		 * Represents a custom Html attribute.
		 * 
		 * @author Joteitti joteitti@cc.hut.fi
		 *
		 */
		private class HtmlAttribute {
			
			public String name;
			public String value;
			
			/**
			 * Constructs a Html attribute.
			 * 
			 * @param name Name to be added.
			 * @param value Value to be added.
			 */
			public HtmlAttribute(String name, String value) {
				this.name = name;
				this.value = value;
			}
			
			/**
			 * Returns a string representation of this Html attribute.
			 */
			public String toString() {
				return name + "='" + value + "'";
			}
		}
	}
	
}
